package org.dreamland.infrastructure.security

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.context.DelegatingSecurityContextRepository
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository
import org.springframework.security.web.context.SecurityContextRepository
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession
import org.springframework.session.web.http.*

@Configuration
@EnableWebSecurity
@EnableJdbcHttpSession
class SecurityConfig(
    private val objectMapper: ObjectMapper
) {

    @Bean
    fun configure(
        http: HttpSecurity,
        authenticationManager: AuthenticationManager,
        securityContextRepository: SecurityContextRepository
    ): SecurityFilterChain {
        http
            .headers { it ->
                it.frameOptions { it.disable() }
            }
            .httpBasic { it.disable() }
            .csrf { it.disable() }
            .authorizeHttpRequests() {
                it.requestMatchers("/auth/**", "/h2/**").permitAll()
                    .anyRequest().authenticated()
            }
            .authenticationManager(authenticationManager)
            .securityContext { it.securityContextRepository(securityContextRepository) }
            .sessionManagement {
                it.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                it.maximumSessions(1)
                it.sessionAuthenticationFailureHandler(JsonAuthenticationFailureHandler(objectMapper))
            }
            .addFilterBefore(
                SmartAuthFilter(),
                UsernamePasswordAuthenticationFilter::class.java
            )
            .exceptionHandling {
                it.authenticationEntryPoint(JsonAuthenticationEntryPoint(objectMapper))
                it.accessDeniedHandler(JsonAccessDeniedHandler(objectMapper))
            }

        return http.build()
    }

    @Bean
    fun securityContextRepository(): SecurityContextRepository {
        return DelegatingSecurityContextRepository(
            HttpSessionSecurityContextRepository(),
            RequestAttributeSecurityContextRepository()
        )
    }


    @Bean
    fun authenticationManager(userDetailsService: UserDetailsService, graphicCaptchaManager: GraphicCaptchaManager): AuthenticationManager {
        val authenticationProvider = UsernamePasswordCaptchaAuthenticationProvider(graphicCaptchaManager, userDetailsService, passwordEncoder())
        val providerManager = ProviderManager(authenticationProvider)
        providerManager.isEraseCredentialsAfterAuthentication = false

        return providerManager
    }


    private fun passwordEncoder(): PasswordEncoder {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder()
    }

    @Bean
    fun cookieSerializer(): CookieSerializer {
        val cookieSerializer = DefaultCookieSerializer()
        cookieSerializer.setCookiePath("/")
        cookieSerializer.setUseSecureCookie(true)
        cookieSerializer.setSameSite("None")
        cookieSerializer.setUseHttpOnlyCookie(true)
        return cookieSerializer
    }

    @Bean
    fun httpSessionIdResolver(cookieSerializer: CookieSerializer): HttpSessionIdResolver {
        val multipleSessionIdResolver = MultipleSessionIdResolver()
        val cookieHttpSessionIdResolver = CookieHttpSessionIdResolver()
        cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer)

        multipleSessionIdResolver.addHttpSessionResolver(cookieHttpSessionIdResolver)
        multipleSessionIdResolver.addHttpSessionResolver(HeaderHttpSessionIdResolver.xAuthToken())
        return multipleSessionIdResolver
    }
}